/*
	book.cc		Open book database
	Copyright (c) 2001, 2002, 2003, 2004, 2010 Kriang Lerdsuwanakij
	email:		lerdsuwa@users.sourceforge.net

	This program is free software; you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation; either version 2 of the License, or
	(at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program; if not, write to the Free Software
	Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/

#include "binfile.h"
#include "book.h"
#include "order.h"
#include "proginfo.h"
#include "rand.h"
#include "gtstream.h"

#include <cstdlib>
#include <vector>
#include <iostream>
#include <stdexcept>

#ifdef _
#undef _
#endif

#ifdef N_
#undef N_
#endif

#include <libintl.h>
#define _(x) gettext(x)
#define N_(x) (x)

#define BOOK_FILE "book.bin"

#define BOOK_DATA_FILE "book.raw"

struct book_next {
	int	pos;
	int	index;
	book_next(int pos_ = 0, int index_ = 0)
		: pos(pos_), index(index_) {}
};

struct book_node {
	std::vector<book_next>	next;
};

// Book is normalized so that the first move is C4.
typedef std::vector<book_node *> book_move_info;
typedef std::vector<book_move_info> book_info;
int symmetry_identity[64] = {
	 0,  1,  2,  3,  4,  5,  6,  7,
	 8,  9, 10, 11, 12, 13, 14, 15,
	16, 17, 18, 19, 20, 21, 22, 23,
	24, 25, 26, 27, 28, 29, 30, 31,
	32, 33, 34, 35, 36, 37, 38, 39,
	40, 41, 42, 43, 44, 45, 46, 47,
	48, 49, 50, 51, 52, 53, 54, 55,
	56, 57, 58, 59, 60, 61, 62, 63
};
	
int symmetry_transpose[64] = {
	 0,  8, 16, 24, 32, 40, 48, 56,
	 1,  9, 17, 25, 33, 41, 49, 57,
	 2, 10, 18, 26, 34, 42, 50, 58,
	 3, 11, 19, 27, 35, 43, 51, 59,
	 4, 12, 20, 28, 36, 44, 52, 60,
	 5, 13, 21, 29, 37, 45, 53, 61,
	 6, 14, 22, 30, 38, 46, 54, 62,
	 7, 15, 23, 31, 39, 47, 55, 63
};

int symmetry_rotate[64] = {
	63, 62, 61, 60, 59, 58, 57, 56,
	55, 54, 53, 52, 51, 50, 49, 48,
	47, 46, 45, 44, 43, 42, 41, 40,
	39, 38, 37, 36, 35, 34, 33, 32,
	31, 30, 29, 28, 27, 26, 25, 24,
	23, 22, 21, 20, 19, 18, 17, 16,
	15, 14, 13, 12, 11, 10,  9,  8,
	 7,  6,  5,  4,  3,  2,  1,  0
};

int symmetry_rotate_transpose[64] = {
	63, 55, 47, 39, 31, 23, 15,  7,
	62, 54, 46, 38, 30, 22, 14,  6,
	61, 53, 45, 37, 29, 21, 13,  5,
	60, 52, 44, 36, 28, 20, 12,  4,
	59, 51, 43, 35, 27, 19, 11,  3,
	58, 50, 42, 34, 26, 18, 10,  2,
	57, 49, 41, 33, 25, 17,  9,  1,
	56, 48, 40, 32, 24, 16,  8,  0
};
#define BOOK_PATH1 "../share/grhino-0.16.1/book/"
const char *get_book_file()
{
	if (get_use_private_files())
		return BOOK_FILE;
	else
		return BOOK_PATH1 BOOK_FILE;
}

const char *get_book_data_file()
{
	if (get_use_private_files())
		return BOOK_DATA_FILE;
	else
		return BOOK_PATH1 BOOK_DATA_FILE;
}

symmetry_type	get_symmetry(int first_move)
{
	switch(first_move) {
		case C4:
			return symmetry_identity;
		case D3:
			return symmetry_transpose;
		case E6:
			return symmetry_rotate_transpose;
		case F5:
			return symmetry_rotate;
		default:
			abort();
	}
}

book_info book;
int	book_depth;
static size_t book_random = 0;

void	book_init()
{
	binary_file_input b(get_book_file());
	book_depth = b.read_int_compress();

	for (int i = 0; i < book_depth; ++i) {
		book.push_back(book_move_info());
	}
	for (int i = 0; i < book_depth; ++i) {
		int num_entry = b.read_int_compress();
		for (int j = 0; j < num_entry; ++j) {
			book_node *n = new book_node;

			int next_moves = b.read_unsigned_char();
			for (int k = 0; k < next_moves; ++k) {
				int pos = b.read_unsigned_char();
				int index;
				if (i != book_depth-1)
					index = b.read_int_compress();
				else
					index = 0;
				n->next.push_back(book_next(pos, index));
			}

			book[i].push_back(n);
		}
	}

					// Check EOF Marker
	if (b.read_int() != 12345) {
		gtstream bufstr;
		gtout(bufstr, _("invalid file format in %$\n"))
			<< get_book_file();
		throw std::runtime_error(bufstr.str());
	}
}

void	set_book_random(size_t r)
{
	book_random = r;
}

size_t	get_book_random()
{
	return book_random;
}

int	book_random_index(size_t size)
{
	switch (book_random) {
		case 5:			// Very high
			if (size > 3) {
					// 9:7:5:3 ratio
				int i = get_random(24);
				return (i < 9) ? 0 : ((i < 16) ? 1 : ((i < 21) ? 2 : 3));
			}
			// Fall through
		case 4:			// High
			if (size > 2) {
					// 7:5:3 ratio
				int i = get_random(15);
				return (i < 7) ? 0 : ((i < 12) ? 1 : 2);
			}
			// Fall through
		case 3:			// Medium
			if (size > 2) {
					// 5:3:1 ratio
				int i = get_random(9);
				return (i < 5) ? 0 : ((i < 8) ? 1 : 2);
			}
			// Fall through
		case 2:			// Low
			if (size > 1) {
					// 3:1 ratio
				int i = get_random(4);
				return (i < 3) ? 0 : 1;
			}
			// Fall through
		case 1:			// Very low
			if (size > 1) {
					// 5:1 ratio
				int i = get_random(6);
				return (i < 5) ? 0 : 1;
			}
			// Fall through
	}
					// None
	return 0;
}

int	book_move(byte_board_info *board, int move_history[NUM_MOVE+1], int player_history[NUM_MOVE+1])
{
	if (board->get_num_move() < 0 || board->get_num_move() >= book_depth+1)
		return POS_INVALID;
	if (board->get_num_move() == 0) {
		int i = get_random(4);
		switch (i) {
			case 0:
				return C4;
			case 1:
				return D3;
			case 2:
				return E6;
			case 3:
				return F5;
		}
	}

	symmetry_type	sym = get_symmetry(move_history[0]);

	int	index = 0;			// Required for second move
	int	move_left = board->get_num_move() -1;
	for (int i = 0; i < book_depth; ++i, --move_left) {

						// Need to pass
		if (player_history[i] == player_history[i+1])
			return POS_INVALID;

						// Found
		if (move_left == 0) {
			if (book[i][index]->next.size() == 0)
				return POS_INVALID;
			int idx = book_random_index(book[i][index]->next.size());
//std::cout << book_random << ' ' << book[i][index]->next.size() << ' ' << idx << '\n';
			return sym[book[i][index]->next[idx].pos];
		}

		int pos = sym[move_history[i+1]];
		int num = book[i][index]->next.size();
		int j = 0;
		for ( ; j < num; ++j) {
			if (book[i][index]->next[j].pos == pos) {
				index = book[i][index]->next[j].index;
				break;
			}
		}
		if (j == num)
			return POS_INVALID;
	}
	return POS_INVALID;
}
